#ifndef SIMULATION_MANAGER_HEADER
#define SIMULATION_MANAGER_HEADER

#include "GL/glfw.h"
#include "glm/glm.hpp"
#include "glm/ext.hpp"
#include <map>

typedef void (*SM_FUNC_PTR)( void );

#define SM_MOUSE_BUTTON_LEFT  GLFW_MOUSE_BUTTON_LEFT
#define SM_MOUSE_BUTTON_RIGHT GLFW_MOUSE_BUTTON_RIGHT
#define SM_MOUSE_BUTTON_PRESS GLFW_PRESS
#define SM_MOUSE_BUTTON_RELEASE GLFW_RELEASE

//Mouse information
struct MouseRec {
	MouseRec() : butt(SM_MOUSE_BUTTON_LEFT),
		state(SM_MOUSE_BUTTON_RELEASE),
		px(0), 
		py(0),
		deltax(0),
		deltay(0) {}

	void UpdateMousePos( unsigned int inpx, unsigned int inpy) {
		deltax = inpx - px;
		deltay = inpy - py;
		px = inpx;
		py = inpy;
	}

	int butt;
	int state;
	unsigned int px, py;
	unsigned int deltax, deltay;
};

class SimulationManager {
public:
	SimulationManager(int w, int h);
	~SimulationManager();

	//Main entry point from main.
	int Run();

	//Exit the simulation
	void Quit();

	//Set callbacks
	void SetCallbackEndFrames( void (*func)() ){
		EndFrames = func;
	}

	void SetCallbackInitWindow( bool (*func)() ) {
		InitWindow = func;
	}

	void SetCallbackInitFrames( void (*func)() ) {
		InitFrames = func;
	}

	void SetCallbackUpdate( void (*func)( float ) ) {
		Update = func;
	}

	void SetCallbackDisplay( void (*func) () ) {
		Display = func;
	}

	void SetCallbackLoadResources( void (*func) () ) {
		LoadResources = func;
	}

	//x, y
	void SetCallbackMouseMove( void GLFWCALL (*func)(int, int) );
	
	//button, state
	void SetCallbackMouseAction( void GLFWCALL (*func)(int, int) );

	void AddKeyCallback( int uniKey, SM_FUNC_PTR inPtr) {
		if (inPtr == NULL) {
			std::map<int, SM_FUNC_PTR>::iterator it = mKeyMap.find(uniKey);
			if (it != mKeyMap.end()){
				mKeyMap.erase(it);
			}
		}
		else {
			mKeyMap[uniKey] = inPtr;
		}
	}
	
	void SetTimeStep( float ts) {mTS = ts;}

	float GetTimeStep() { return mTS; }

	//Pauses simulation
	void Pause() { 
		mIsPaused = !mIsPaused;
	}


	glm::mat4x4 PerspectiveProjectionMatrix(  float fovDeg, float aspect, float znear, float zfar );

	glm::mat4x4 OrthogonalProjectionMatrix( float left, float right, float bottom, float top, float znear, float zfar );

	void SetLookAt(const float * eyepos, const float * lookatpos, const float * up );

	void SetModelviewMatrix( const float * mvmat );

	void SetProjectionMatrix( const float * projmat );

private:

	//Being Main Loop
	void MainLoop();

	//Pools for user input.
	void PollUserInput();

	void InitInputCallbacks();

	bool IsTimeToUpdate(double t);

//*** Callbacks
	//This is for standard keys.
	static void GLFWCALL KeyboardCallback(int uniKey, int state) 
	{
		printf("unikey: %d\n", uniKey);

		if (state == GLFW_PRESS) {
			printf("Key pressed: %d\n", uniKey);
		}

		if (glfwGetKey(uniKey)){
			printf("***in KeyboardCallback: %d", uniKey);
		}

		if (state == GLFW_RELEASE) {
			printf("Key release\n");
		}
	}



	bool (*InitWindow)();

	void (*InitFrames)();

	void (*Update)( float );

	void (*Display)();

	void (*EndFrames)();

	void (*LoadResources)();

private:

	glm::vec3 mDataCenter;

	//Projection matrix
	glm::mat4x4 projMat;
	
	//Modelview matrix
	glm::mat4x4 mvMat;

	//Update time step
	float mTS;
	int mWindowW;
	int mWindowH;

	//
	bool mIsRunning;

	bool mIsPaused;

	//Default functions
	void _DefaultSMUpdate( float ts );

	bool _DefaultSMInitWindow(int w, int h);
	
	void _DefaultSMInitFrames();

	void _DefaultSMDisplay();

	void _DefaultSMEndFrames();

	void _DefaultSMLoadResources();


	std::map< int, SM_FUNC_PTR > mKeyMap;
};

#endif